package conf

import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"sync"
	"time"

	"gitee.com/hexug/devcloud/mcenter/consted"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)

var (
	conf *viper.Viper
	db   *dbClient
	lock sync.Mutex
)

func C() *viper.Viper {
	if conf == nil {
		LoadConfig("etc/config.toml")
	}
	return conf
}

// 设置参数的默认值
func setDefault(v *viper.Viper) {
	//用户部分
	v.SetDefault("auth.username", consted.USER_USERNAME)
	v.SetDefault("auth.password", consted.USER_PASSWORD)

	//mongodb部分
	v.SetDefault("mongo.endpoint", []string{"127.0.0.1:27017"})
	v.SetDefault("mongo.database", "defaulttest")
	//日志部分
	v.SetDefault("log.savefiledir", "./logs")
	v.SetDefault("log.savefile", false)
	v.SetDefault("log.loglevel", "debug")
	//默认8小时
	v.SetDefault("token.timeout", 28800)
}

func LoadConfig(path string) {
	conf = viper.New()
	setDefault(conf)
	conf.SetConfigName("config") // 指定配置文件名称（不需要带后缀）
	conf.SetConfigType("toml")   // 指定配置文件类型
	// C.AddConfigPath("./etc/") // 指定查找配置文件的路径（这里使用相对路径）
	// C.AddConfigPath(path)     // 指定查找配置文件的路径（这里使用相对路径）
	conf.SetConfigFile(path)
	conf.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	conf.AutomaticEnv()
	if err := conf.ReadInConfig(); err != nil { // 读取配置信息失败
		re, er := regexp.Compile(".")
		if er != nil {
			panic(er.Error())
		}
		if !re.MatchString(err.Error()) {
			panic(err.Error())
		}
		fmt.Println(err)
		fmt.Println(fmt.Errorf("读取配置文件失败: %s", "文件未找到"))
	}
	conf.OnConfigChange(func(e fsnotify.Event) {
		// 配置文件发生变更之后会调用的回调函数
		fmt.Println("Config file changed:", e.Name)
	})
	conf.WatchConfig()
}

// 数据库客户端集
type dbClient struct {
	MongoClient *mongo.Database
}

// 对外暴露的函数
func DB() *dbClient {
	lock.Lock()
	defer lock.Unlock()
	if db == nil {
		db = &dbClient{}
		db.clint()
	}
	return db
}

// 管理客户端
func (d *dbClient) clint() {
	mongoC, err := d.getMongoClint()
	if err != nil {
		panic(err)
	}
	d.MongoClient = mongoC.Database(C().Get("mongo.database").(string))
}

// 生成mongodb的客户端
func (d *dbClient) getMongoClint() (*mongo.Client, error) {
	//以参数的形式创建客户端
	opts := options.Client()
	//如果设置了密码，就需要这一步
	if C().Get("mongo.authsource") != nil || C().Get("mongo.password") != nil {
		cred := options.Credential{
			//认证用户名密码的库，默认就是admin库
			AuthSource: C().Get("mongo.authsource").(string),
			//用户名
			Username: C().Get("mongo.username").(string),
			//密码
			Password: C().Get("mongo.password").(string),
			//如果有密码，这里必须为true
			PasswordSet: true,
		}
		//用密码就需要添加下认证
		opts.SetAuth(cred)
	}
	c := C().Get("mongo.endpoint").([]interface{})
	addrs := []string{}
	for _, addr := range c {
		addrs = append(addrs, addr.(string))
	}
	//设置主机，可以设置多个主机
	opts.SetHosts(addrs).
		//设置连接超时
		SetConnectTimeout(5 * time.Second).
		//设置连接池
		SetMaxPoolSize(200)
	// 设置超时
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*5))
	defer cancel()
	ch := make(chan struct{}, 1)
	var (
		client *mongo.Client
		err    error
	)
	go func(ct context.Context, workDone chan struct{}) {
		// 创建新客户端并连接到服务器
		client, err = mongo.Connect(ctx, opts)
		workDone <- struct{}{}
	}(ctx, ch)
	select { //下面的case只执行最早到来的那一个
	case <-ch:
		if err != nil {
			return nil, err
		}
		//ping测下，看是否存活
		if err := client.Ping(context.TODO(), readpref.Primary()); err != nil {
			return nil, err
		}
		fmt.Println("MongoDB连接成功")
		return client, nil
	case <-ctx.Done(): //ctx.Done()是一个管道，context超时或者调用了cancel()都会关闭这个管道，然后读操作就会立即返回
		return nil, fmt.Errorf("连接服务端 %s 超时", C().Get("mongo.endpoint"))
	}
}
